﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SiteParser.Core.ParserEntities;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using SiteParser.Core.Resources;
using SiteParser.Core.Configuration;
using System.IO;
using SiteParser.Core.Logging;
using SiteParser.Core.Utilites;

namespace SiteParser.Core
{
	public class ConfigParserBuilder
	{
		public ParserEntity BuildParser(XmlReader configReader) {
			if (configReader == null) {
				throw new ArgumentNullException("configReader");
			}

			XmlSerializer xmlSerizer = new XmlSerializer(typeof(ItemType));
			ItemType itemType = (ItemType)xmlSerizer.Deserialize(configReader);

			ItemParser entity = new ItemParser();

			ProcessItem(itemType, entity);

			return entity;
		}

		public ParserEntity BuildParser(string configText, ConfigType configType) {
			if (string.IsNullOrEmpty(configText)) {
				throw new ArgumentNullException("configText");
			}

			switch (configType) {
				case ConfigType.ConfigFile:
					using (var reader = XmlReader.Create(PathUtilities.NormalizePath(configText))) {
						return BuildParser(reader);
					}
				case ConfigType.ConfigText:
					using (var reader = XmlReader.Create(new StringReader(configText))) {
						return BuildParser(reader);
					}
			}

			return null;
		}

		public ParserEntity BuildParser() {
			return BuildParser(ParserConfigSection.Instance.ConfigFile, ConfigType.ConfigFile);
		}

		#region ItemCollections processing

		private void ProcessItems(ItemType[] items, ParserEntity parserEntity)
		{
			if (items != null) {
				foreach (var item in items) {
					var itemParser = new ItemParser();
					ProcessItem(item, itemParser);
					parserEntity.ChildEntities.Add(itemParser);
				}
			}
		}

		private void ProcessReferences(ReferenceType[] references, ParserEntity parserEntity) {
			if (references != null) {
				foreach (var reference in references) {
					var referenceParser = new ReferenceParser();
					ProcessReference(reference, referenceParser);
					parserEntity.ChildEntities.Add(referenceParser);
				}
			}
		}

		private void ProcessCollections(CollectionType[] collections, ParserEntity parserEntity)
		{
			if (collections != null) {
				foreach (var collection in collections) {
					var collectionParser = new ListHtmlParser();
					ProcessCollection(collection, collectionParser);
					parserEntity.ChildEntities.Add(collectionParser);
				}
			}
		}

		private void ProcessPaging(PagingType[] paging, ParserEntity parserEntity) {
			if (paging != null) {
				foreach (var pager in paging) {
					var pagerParser = new PagingParser();
					ProcessPaging(pager, pagerParser);
					parserEntity.ChildEntities.Add(pagerParser);
				}
			}
		}

		private void ProcessResources(ResourceType[] resources, ParserEntity parserEntity) {
			if (resources != null) {
				foreach (var resource in resources) {
					var resourceParser = new ResourceParser();
					ProcessResource(resource, resourceParser);
					parserEntity.ChildEntities.Add(resourceParser);
				}
			}
		}

		#endregion ItemCollections processing

		private void ProcessBaseItem(BaseType baseItem, ParserEntity parserEntity) {
			parserEntity.PropertyName = baseItem.propertyName;
			parserEntity.FormatterName = baseItem.formatter;

			parserEntity.Expression = new HtmlNavigationExpression();

			if (baseItem.SelectExpressionConfig != null) {
				ProcessNavigationConfiguration(baseItem.SelectExpressionConfig, parserEntity, parserEntity.Expression);
			}
		}

		private void ProcessItem(ItemType item, ItemParser parserEntity) {
			ProcessBaseItem(item, parserEntity);

			parserEntity.PageBreak = item.pageBreak;

			ProcessItems(item.Item, parserEntity);
			ProcessCollections(item.Collection, parserEntity);
			ProcessReferences(item.Reference, parserEntity);
			ProcessPaging(item.Paging, parserEntity);
			ProcessResources(item.Resource, parserEntity);
		}

		private void ProcessReference(ReferenceType reference, ReferenceParser parserEntity) {
			ProcessBaseItem(reference, parserEntity);

			parserEntity.ReadCacheEnabled = reference.readCacheEnabled;
			parserEntity.WriteCacheEnabled = reference.writeCacheEnabled;
			parserEntity.EncodingName = reference.encoding;

			ProcessItems(reference.Item, parserEntity);
			ProcessCollections(reference.Collection, parserEntity);
			ProcessReferences(reference.Reference, parserEntity);
			ProcessPaging(reference.Paging, parserEntity);
			ProcessResources(reference.Resource, parserEntity);
		}

		private void ProcessPaging(PagingType paging, PagingParser parserEntity) {
			ProcessBaseItem(paging, parserEntity);

			parserEntity.MaximumIterationCount = paging.maximumIterationCount;
			parserEntity.StartIndex = paging.startIndex;
			parserEntity.UrlIdRegexPattern = paging.urlIdRegex;

			parserEntity.ReadCacheEnabled = paging.readCacheEnabled;
			parserEntity.WriteCacheEnabled = paging.writeCacheEnabled;
			parserEntity.EncodingName = paging.encoding;

			ItemTemplateHtmlParser itemTemplateParser;

			itemTemplateParser = new ItemTemplateHtmlParser();
			ProcessItemTemplate(paging.ItemTemplate, itemTemplateParser);
			parserEntity.ChildEntities.Add(itemTemplateParser);
		}

		private void ProcessItemTemplate(ItemType itemTemplate, ItemTemplateHtmlParser parserEntity) {
			ProcessBaseItem(itemTemplate, parserEntity);

			ProcessItems(itemTemplate.Item, parserEntity);
			ProcessCollections(itemTemplate.Collection, parserEntity);
			ProcessReferences(itemTemplate.Reference, parserEntity);
			ProcessPaging(itemTemplate.Paging, parserEntity);
			ProcessResources(itemTemplate.Resource, parserEntity);
		}

		private void ProcessCollection(CollectionType collection, ListHtmlParser parserEntity) {
			ProcessBaseItem(collection, parserEntity);

			parserEntity.MaximumIterationCount = collection.maximumIterationCount;
			parserEntity.StartIndex = collection.startIndex;

			ItemTemplateHtmlParser itemTemplateParser;
			itemTemplateParser = new ItemTemplateHtmlParser()
			{
				StartIndex = collection.startIndex,
				MaximumIterationCount = collection.maximumIterationCount
			};

			ProcessItemTemplate(collection.ItemTemplate, itemTemplateParser);
			parserEntity.ChildEntities.Add(itemTemplateParser);
		}

		private void ProcessResource(ResourceType resource, ResourceParser parserEntity) {
			ProcessBaseItem(resource, parserEntity);
			parserEntity.ResourcesDirectory = resource.path;
		}

		private void ProcessNavigationConfiguration(SelectExpressionConfigType selectExpressionConfig, ParserEntity parserEntity, HtmlNavigationExpression navigationExpression) {
			if (selectExpressionConfig != null) {
				var selectConfig = selectExpressionConfig;

				navigationExpression.SelectType = selectConfig.selectTypeSpecified ?
					(SelectTypes)Enum.Parse(typeof(SelectTypes), selectConfig.selectType.ToString(), true) : SelectTypes.InnerHtml;

				navigationExpression.SelectingAttributeName = selectConfig.selectingAttributeName;
				navigationExpression.SelectConstantValue = selectConfig.selectConstantValue;
				navigationExpression.MarkerValue = selectConfig.markerValue;
				navigationExpression.RegexValue = selectConfig.regexValue;
				navigationExpression.XPathValue = selectConfig.xPathValue;

				var tagMathingRule = selectConfig.TagMatchingRule;
				if (tagMathingRule != null && !string.IsNullOrEmpty(tagMathingRule.tagName)) {
					MatchType matchType = tagMathingRule.matchTypeSpecified ?
						(MatchType)Enum.Parse(typeof(MatchType), tagMathingRule.matchType.ToString(), true) : MatchType.Direct;
					navigationExpression.TagMatchingRule = new MatchingRule() { Name = tagMathingRule.tagName, Type = matchType, MatchIndex = tagMathingRule.tagIndex };
				}

				var attributeFilter = selectConfig.AttributeFilter;
				if (attributeFilter != null && attributeFilter.Length > 0) {
					foreach (var matchingRule in attributeFilter) {
						if (!string.IsNullOrEmpty(matchingRule.attributeName)) {
							var matchType = matchingRule.matchTypeSpecified ?
								(MatchType)Enum.Parse(typeof(MatchType), matchingRule.matchType.ToString(), true) : MatchType.Direct;

							navigationExpression.AttributesFilter[matchingRule.attributeName] = new MatchingRule() { Name = matchingRule.attributeValue, Type = matchType };
						}
					}
				}
			}
		}
	}

	public enum ConfigType
	{
		ConfigText,
		ConfigFile
	}
}
